import os
import PyPDF2
import logging
from openai import OpenAI
import tkinter as tk
from tkinter import scrolledtext, messagebox
import time
import tkinter.font as tkFont
import re
import hashlib
import pickle
from collections import defaultdict

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('user_manual_qa.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# 配置
MANUAL_PATH = "D:/现代帕里斯帝用户手册/现代汽车帕里斯帝用户手册.pdf"
MAX_CONTEXT_LENGTH = 15000  # 增大上下文长度
MAX_RETRY = 3
RETRY_DELAY = 1
CACHE_FILE = "manual_cache.pkl"


class ManualQAApp:
    def __init__(self, root):
        self.root = root
        self.root.title("现代帕里斯帝用户手册咨询系统 v2.0")
        self.root.geometry("1000x800")

        # 初始化字体
        self.available_fonts = list(tkFont.families())
        self.chinese_font = self.select_chinese_font()
        logger.info(f"使用字体: {self.chinese_font}")

        # 初始化豆包客户端
        self.client = OpenAI(
            base_url="https://ark.cn-beijing.volces.com/api/v3",
            api_key=os.environ.get("DOUBAO_API_KEY"),
        )

        # 加载用户手册
        self.manual_text = self.load_full_manual()
        if not self.manual_text:
            messagebox.showerror("错误", "无法加载用户手册PDF文件！")
            self.root.destroy()
            return

        # 预处理手册结构
        self.section_index = self.build_section_index()

        # 创建UI
        self.create_widgets()

    def select_chinese_font(self):
        """选择系统中可用的最佳中文字体"""
        preferred_fonts = [
            "Microsoft YaHei",
            "微软雅黑",
            "SimHei",
            "黑体",
            "Arial Unicode MS",
            "Arial"
        ]
        for font in preferred_fonts:
            if font in self.available_fonts:
                return font
        return "TkDefaultFont"

    def load_full_manual(self):
        """完整加载PDF手册并提取所有文本（带缓存）"""
        try:
            # 计算文件哈希作为缓存键
            file_hash = hashlib.md5(open(MANUAL_PATH, 'rb').read()).hexdigest()

            if os.path.exists(CACHE_FILE):
                try:
                    with open(CACHE_FILE, 'rb') as f:
                        cache = pickle.load(f)
                        if cache.get('hash') == file_hash:
                            logger.info("从缓存加载手册内容")
                            return cache['text']
                except Exception as e:
                    logger.warning(f"缓存加载失败: {str(e)}")

            logger.info(f"开始解析PDF手册: {MANUAL_PATH}")
            full_text = ""

            with open(MANUAL_PATH, 'rb') as file:
                reader = PyPDF2.PdfReader(file)
                total_pages = len(reader.pages)

                for i, page in enumerate(reader.pages, 1):
                    text = page.extract_text()
                    if text:
                        # 增强PDF文本清洗
                        text = re.sub(r'\s+', ' ', text)  # 合并空白字符
                        text = re.sub(r'([^\n])\n([^\n])', r'\1 \2', text)  # 修复错误换行
                        full_text += f"\n=== 第{i}页 ===\n{text}\n"

                    if i % 50 == 0 or i == total_pages:
                        logger.info(f"已解析 {i}/{total_pages} 页")

            # 保存缓存
            try:
                with open(CACHE_FILE, 'wb') as f:
                    pickle.dump({'hash': file_hash, 'text': full_text}, f)
            except Exception as e:
                logger.warning(f"缓存保存失败: {str(e)}")

            logger.info(f"手册加载完成，共{len(full_text)}字符")
            return full_text

        except Exception as e:
            logger.error(f"手册加载失败: {str(e)}")
            return None

    def build_section_index(self):
        """构建章节索引"""
        index = defaultdict(list)
        # 匹配章节标题 (如 "第1章 简介" 或 "3.2 座椅调节")
        section_pattern = r'(?:第[一二三四五六七八九十]+章|[0-9]+(?:\.[0-9]+)*)\s+.+?(?=\n第|\n\d|$)'

        matches = re.finditer(section_pattern, self.manual_text)
        for match in matches:
            section_title = match.group().strip()
            start_pos = match.start()
            end_pos = self.manual_text.find("\n第", start_pos + 1)
            content = self.manual_text[start_pos:end_pos if end_pos != -1 else None]
            index[section_title] = content[:10000]  # 每章节最多保存前10000字符

        logger.info(f"已建立{len(index)}个章节索引")
        return index

    def create_widgets(self):
        """创建界面组件"""
        # 标题
        title_frame = tk.Frame(self.root, bg="#2c3e50")
        title_frame.pack(fill=tk.X, padx=10, pady=10)

        title_label = tk.Label(
            title_frame,
            text="现代帕里斯帝用户手册智能咨询系统",
            font=(self.chinese_font, 18, "bold"),
            fg="white",
            bg="#2c3e50"
        )
        title_label.pack(pady=5)

        # 问题输入区
        input_frame = tk.Frame(self.root)
        input_frame.pack(fill=tk.X, padx=15, pady=10)

        tk.Label(
            input_frame,
            text="请输入您的问题:",
            font=(self.chinese_font, 12),
            anchor="w"
        ).pack(fill=tk.X)

        self.question_entry = tk.Entry(
            input_frame,
            font=(self.chinese_font, 14),
            width=80
        )
        self.question_entry.pack(fill=tk.X, pady=5)
        self.question_entry.bind("<Return>", lambda e: self.ask_question())

        # 按钮区
        button_frame = tk.Frame(self.root)
        button_frame.pack(pady=10)

        buttons = [
            ("提问", self.ask_question, "#3498db"),
            ("清空对话", self.clear_conversation, "#e74c3c"),
            ("退出系统", self.root.destroy, "#95a5a6")
        ]

        for text, command, color in buttons:
            btn = tk.Button(
                button_frame,
                text=text,
                command=command,
                font=(self.chinese_font, 12),
                bg=color,
                fg="white",
                width=15,
                relief=tk.FLAT
            )
            btn.pack(side=tk.LEFT, padx=10)

        # 对话历史区
        history_frame = tk.LabelFrame(
            self.root,
            text="咨询记录",
            font=(self.chinese_font, 12)
        )
        history_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=5)

        self.history_text = scrolledtext.ScrolledText(
            history_frame,
            font=(self.chinese_font, 12),
            wrap=tk.WORD,
            padx=10,
            pady=10,
            bg="#f9f9f9"
        )
        self.history_text.pack(fill=tk.BOTH, expand=True)
        self.history_text.config(state=tk.DISABLED)

        # 状态栏
        self.status_var = tk.StringVar()
        self.status_var.set("就绪 | 已加载手册，共 {} 页".format(
            len(re.findall(r'=== 第\d+页 ===', self.manual_text)))
        )

        status_bar = tk.Label(
            self.root,
            textvariable=self.status_var,
            relief=tk.SUNKEN,
            anchor=tk.W,
            font=(self.chinese_font, 10),
            bg="#ecf0f1"
        )
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

        # 配置文本样式
        self.history_text.tag_config("user", foreground="#2c3e50", font=(self.chinese_font, 12, "bold"))
        self.history_text.tag_config("assistant", foreground="#27ae60", font=(self.chinese_font, 12))
        self.history_text.tag_config("reference", foreground="#7f8c8d", font=(self.chinese_font, 10))

    def ask_question(self):
        """处理用户提问"""
        question = self.question_entry.get().strip()
        if not question:
            messagebox.showwarning("提示", "请输入您的问题！")
            return

        self.display_message(f"您: {question}", "user")
        self.question_entry.delete(0, tk.END)
        self.status_var.set("正在分析您的问题...")
        self.root.update()

        try:
            start_time = time.time()

            # 获取最相关的上下文
            context = self.get_smart_context(question)
            logger.info(f"上下文提取耗时: {time.time() - start_time:.2f}s")

            if not context:
                self.display_message("客服: 抱歉，无法找到相关手册内容", "assistant")
                return

            # 构建优化的提示词
            prompt = self.build_enhanced_prompt(question, context)

            # 调用大模型
            self.status_var.set("正在查询手册内容...")
            self.root.update()

            response = self.query_doubao(prompt)

            # 显示结果
            self.display_message(f"客服:\n{response}", "assistant")
            self.status_var.set(f"已回答 | 耗时: {time.time() - start_time:.1f}秒")

        except Exception as e:
            logger.error(f"提问出错: {str(e)}")
            self.display_message("客服: 处理问题时出错，请稍后再试", "assistant")
            self.status_var.set("错误: " + str(e))

    def get_smart_context(self, question):
        """智能获取与问题最相关的上下文"""
        try:
            # 1. 提取问题关键词
            keywords = set(re.findall(r'[\w\u4e00-\u9fff]{2,}', question.lower()))
            logger.info(f"问题关键词: {keywords}")

            # 2. 优先从章节索引中查找
            matched_sections = []
            for section, content in self.section_index.items():
                if any(kw in section.lower() for kw in keywords):
                    matched_sections.append((section, content))

            # 3. 如果找到匹配章节，组合内容
            if matched_sections:
                context = "\n\n".join([f"【{sec}】\n{cont}" for sec, cont in matched_sections[:3]])
                return context[:MAX_CONTEXT_LENGTH]

            # 4. 全文档关键词搜索
            paragraphs = [p.strip() for p in self.manual_text.split('\n') if 50 < len(p.strip()) < 1000]

            # 5. 计算段落相关性
            scored_paras = []
            for para in paragraphs:
                score = sum(para.lower().count(kw) for kw in keywords)
                if score > 0:
                    scored_paras.append((score, para))

            # 6. 组合最相关段落
            scored_paras.sort(reverse=True, key=lambda x: x[0])
            context = "\n\n".join([p for s, p in scored_paras[:20]])

            return context if context else self.manual_text[:MAX_CONTEXT_LENGTH]

        except Exception as e:
            logger.error(f"获取上下文出错: {str(e)}")
            return self.manual_text[:MAX_CONTEXT_LENGTH]

    def build_enhanced_prompt(self, question, context):
        """构建优化的提示词"""
        return f"""作为现代汽车专业客服，请严格根据以下手册内容回答问题：

【用户问题】
{question}

【相关手册内容】
{context}

【回答要求】
1. 必须基于手册内容回答，不得编造信息
2. 引用原文时注明页码（如"参见第25页"）
3. 分步骤说明操作流程
4. 若无直接答案，提供最接近的3条相关信息

请按以下格式回答：
📌 主要答案：...
🔍 详细说明：
1. ...
2. ...
📖 参考位置：..."""

    def query_doubao(self, prompt):
        """查询豆包大模型"""
        for attempt in range(MAX_RETRY):
            try:
                start_time = time.time()
                logger.info(f"第{attempt + 1}次API调用，提示词长度: {len(prompt)}")

                response = self.client.chat.completions.create(
                    model="deepseek-v3-250324",
                    messages=[
                        {
                            "role": "system",
                            "content": "你是现代汽车专业客服，需严格根据提供的手册内容回答问题"
                        },
                        {
                            "role": "user",
                            "content": prompt
                        }
                    ],
                    temperature=0.1,
                    max_tokens=2500,
                    timeout=30
                )

                logger.info(f"API调用成功，耗时: {time.time() - start_time:.2f}s")

                if response.choices and response.choices[0].message.content:
                    return response.choices[0].message.content.strip()

            except Exception as e:
                logger.error(f"第{attempt + 1}次尝试失败: {str(e)}")
                if attempt < MAX_RETRY - 1:
                    time.sleep(RETRY_DELAY * (attempt + 1))
                else:
                    raise Exception(f"API调用失败: {str(e)}")

        return "抱歉，暂时无法获取回答。请检查网络连接或稍后再试。"

    def display_message(self, message, tag):
        """显示消息到对话历史"""
        self.history_text.config(state=tk.NORMAL)
        self.history_text.insert(tk.END, "\n\n" if tag == "user" else "\n", None)
        self.history_text.insert(tk.END, message, tag)
        self.history_text.see(tk.END)
        self.history_text.config(state=tk.DISABLED)

    def clear_conversation(self):
        """清空对话历史"""
        self.history_text.config(state=tk.NORMAL)
        self.history_text.delete(1.0, tk.END)
        self.history_text.config(state=tk.DISABLED)
        self.status_var.set("对话已清空 | 就绪")


if __name__ == "__main__":
    try:
        root = tk.Tk()
        root.option_add("*Font", "TkDefaultFont")
        app = ManualQAApp(root)
        root.mainloop()
    except Exception as e:
        logger.error(f"程序崩溃: {str(e)}")
        messagebox.showerror("错误", f"程序发生严重错误:\n{str(e)}")